package se.webbzon.boltzmann.game.object.waypoint;

import java.util.Collection;
import java.util.HashSet;

import javax.vecmath.Point3d;

import se.webbzon.boltzmann.debug.Debug;
import se.webbzon.oschi01.worldarea.WorldArea;
import se.webbzon.oschi01.worldobject.WorldObject;
import se.webbzon.oschi01.worldobject.WorldStaticObject;

public abstract class AbstractDirection {

	// The tolerance distance for this abstract direction
	private static final double triggerDistance = 4;
	
	// A collection of all the listeners to this abstract direction
	private final Collection<DirectionListener> listeners;
	
	// The distance checker instance of this abstract direction
	private final WorldObject distanceChecker;
	
	// The world object associated with this abstract direction object
	private final WorldStaticObject directionTaker;
		
	// The start point of this abstract direction
	private final Waypoint start;
	
	// The current waypoint of this abstract direction
	private Waypoint waypoint;
	
	/*============================================================
	Constructors
	============================================================*/
	
	/** Creates a new abstract direction associated with the provided
	 * direction taker and the path of the given waypoint. **/
	public AbstractDirection(WorldStaticObject directionTaker, Waypoint start) {
		this.distanceChecker = new WorldObjectImpl();
		this.directionTaker = directionTaker;
		this.start = start;
		listeners = new HashSet<DirectionListener>();
		waypoint = start;
	}
	
	/*============================================================
	Public Methods
	============================================================*/
	
	/** Adds a listener to this direction instance. **/
	public boolean addListener(DirectionListener listener) {
		int d;
		Debug.debugCollection(this, listeners);
		return listeners.add(listener);
	}
	
	/** Adds a listener to this direction instance. **/
	public boolean removeListener(DirectionListener listener) {
		return listeners.remove(listener);
	}
	
	/** Clears all listeners in this direction instance. **/
	public void clearListeners() {
		listeners.clear();
	}
	
	/** Returns the direction taker of this direction instance. **/
	public WorldStaticObject getDirectionTaker() {
		return directionTaker;
	}
	
	/** Starts the direction instance. **/
	public void begin(WorldArea world) {
		world.createInstance(distanceChecker);
		onBegin();
		moveTo(waypoint.getPosition());
	}
	
	/** Stops the direction instance. **/
	public void end(WorldArea world) {
		onEnd();
		world.destroyInstance(distanceChecker);
	}
	
	/*============================================================
	Protected Methods
	============================================================*/
	
	/** Commands the direction taker to move towards the given point. **/
	protected abstract void moveTo(Point3d position);
	
	/** This method is invoked when the direction instance wants to take
	 * control of the direction taker. **/
	protected abstract void onBegin();
	
	/** This method is invoked when the direction instance wants to 
	 * release the control of the direction taker. **/
	protected abstract void onEnd();
	
	/*============================================================
	Private Classes
	============================================================*/
	
	private final class WorldObjectImpl extends WorldObject {

		/*============================================================
		Constructors
		============================================================*/
		
		public WorldObjectImpl() {
			super(WorldObject.onStep);
		}
		
		/*============================================================
		Public Methods
		============================================================*/
		
		@Override public void onStep(WorldArea world) {
			if (Math.abs(directionTaker.getX() - waypoint.getX()) <= triggerDistance &&
				Math.abs(directionTaker.getZ() - waypoint.getZ()) <= triggerDistance) {
				Waypoint next = waypoint.getNext();
				if (next == null) {
					end(getWorld());
					for (DirectionListener listener : listeners.toArray(new DirectionListener[0]))
						listener.targetReached(AbstractDirection.this);
					waypoint = start;
				} else {
					waypoint = next;
					moveTo(waypoint.getPosition());
				}
			}
		}
		
	}

}
